﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections;
using System.Linq;
using ScrumTable.BL.DM.Properties;
using ScrumTable.Common;
using ScrumTable.Common.Collections;
using ScrumTable.DL.Data.Common;
using ScrumTable.DL.Data.Schema;
using System.Collections.Generic;

#endregion

namespace ScrumTable.BL.DM.DataManagement
{
    /// <summary>
    /// Contains the common facilities for a scrum (backlog) element
    /// (UserStory / Task / Bug /...).
    /// </summary>
    public abstract class ScrumItemBase : DomainBase, IScrumItem
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private DomainCollection<Attachment> _attachments;
        private bool _isStateReady = true;
        private ScrumState _lastOkState;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Gets a list of all attachments associated to the current scrum
        /// item.
        /// </summary>
        public DomainCollection<Attachment> Attachments
        {
            get
            {
                if (_attachments == null)
                {
                    _attachments = new DomainCollection<Attachment>(
                        false,
                        Context,
                        this,
                        ContextInternal.Data.Storage[Schema.AttachmentSchema]);
                }
                return _attachments;
            }
        }


        /// <summary>
        /// Gets a dynamic mapped property with the given schema name. The
        /// specified name must be mapped by the underlying schema as a custom
        /// (additional) field mapping.
        /// </summary>
        /// <param name="name">Specifies the schema property name (schema entry name) which mapped value should be retrieved.</param>
        /// <returns>Returns the retrieved value.</returns>
        public object this[string name]
        {
            get
            {
                EnsureNotDisposed();
                EnsureUserSchemaAvailable(name);
                return this[DomainSchema[name]];
            }
            set
            {
                EnsureNotDisposed();
                EnsureUserSchemaAvailable(name);
                this[DomainSchema[name]] = value;
            }
        }


        /// <summary>
        /// Returns the ranking value.
        /// </summary>
        public double? Ranking
        {
            get { return DataObject.GetData(RankingSchema, data => (double)data, new double?()); }
            set { SetDataWithUndoAction(RankingSchema, value); }
        }

        /// <summary>
        /// Gets the name of the current domain object. In most cases this
        /// property will contain the domain object title.
        /// </summary>
        public override string Name
        {
            get { return DataObject.GetData(NameSchema, string.Empty); }
            set { SetDataNotNullWithUndoAction(NameSchema, value); }
        }

        /// <summary>
        /// Gets/sets the description.
        /// </summary>
        public string Description
        {
            get { return DataObject.GetData(DescriptionSchema, string.Empty); }
            set { SetDataWithUndoAction(DescriptionSchema, value); }
        }


        /// <summary>
        /// Specifies the assigned project member.
        /// </summary>
        public Member AssignedMember
        {
            get
            {
                return DataObject.GetData(
                    AssignedMemberSchema,
                    data =>
                    {
                        return ParentProject.Members.ContainsId(((ValueChain<string>)data).Top)
                            ? ParentProject.Members[((ValueChain<string>)data).Top]
                            : null;
                    });
            }
            set
            {
                SetDataWithUndoAction(
                    AssignedMemberSchema,
                    (value != null) ? new ValueChain<string>(ParentProject.IdChain, value.Id) : null);
            }
        }

        /// <summary>
        /// Gets or sets the lifecycle state of the current scrum element.
        /// </summary>
        public ScrumState State
        {
            get
            {
                string state = DataObject.GetData(StateSchema, ItemType.StateMappings.First().Name);
                return ItemType.StateMappings[state];
            }
            set
            {
                PreCondition.AssertNotNull(value, "value");
                value = ItemType.StateMappings[value.Id];

                ItemType.ValidateStateChange(State, value);
                _lastOkState = State;

                using (WatchForSynchronizedChange(() => StateIsReady = IsSynchronized))
                {
                    SetDataWithUndoAction(StateSchema, value.ToString());
                }
            }
        }

        /// <summary>
        /// Returns true if the lifecycle of the current element is ready
        /// to perform additional changes.
        /// </summary>
        public bool StateIsReady
        {
            get
            {
                if (!ItemType.HasStrictWorkflow)
                {
                    return true;
                }
                return _isStateReady;
            }
            protected set
            {
                if (ItemType.HasStrictWorkflow && value != StateIsReady)
                {
                    FirePropertyChanging("StateIsReady");
                    _isStateReady = value;
                    FirePropertyChanged("StateIsReady");
                    if(_isStateReady)
                    {
                        _lastOkState = null;
                    }
                }
            }
        }

        /// <summary>
        /// Gets true if the changes have been synchronized with the back-end system.
        /// </summary>
        public override bool IsSynchronized
        {
            get { return base.IsSynchronized; }
            protected set
            {
                base.IsSynchronized = value;

                // update ready state in conjunction with the IsSynchronized flag
                StateIsReady = IsSynchronized;
                
            }
        }

        /// <summary>
        /// Gets the ScrumItemType of the ScrumItem
        /// </summary>
        public abstract ScrumItemType ItemType { get; }
 
        /// <summary>
        /// Gets a collection which contains all child collections of the current
        /// domain object.
        /// </summary>
        internal override ICollection ChildCollections { get { return new[] { Attachments }; } }


        /// <summary>
        /// Returns the name-field schema information.
        /// </summary>
        protected abstract SchemaBase NameSchema { get; }
        
        /// <summary>
        /// Returns the description-field schema information.
        /// </summary>
        protected abstract SchemaBase DescriptionSchema { get; }
        
        /// <summary>
        /// Returns the memberid-field schema information.
        /// </summary>
        protected abstract SchemaBase AssignedMemberSchema { get; }

        /// <summary>
        /// Returns the ranking-field schema information.
        /// </summary>
        protected abstract SchemaBase RankingSchema { get; }

        /// <summary>
        /// Returns the state-field schema information.
        /// </summary>
        protected abstract SchemaBase StateSchema { get; }


        /// <summary>
        /// Gets a dynamic mapped property with the given schema name. The
        /// specified name must be mapped by the underlying schema as a custom
        /// (additional) field mapping.
        /// </summary>
        /// <param name="schema">Specifies the name of the schema entry which mapped value should be retrieved.</param>
        /// <returns>Returns the retrieved value.</returns>
        private object this[SchemaBase schema]
        {
            get
            {
                PreCondition.AssertNotNull(schema, "schema");
                EnsureNotDisposed();

                return DataObject.GetData(schema, (object)null);
            }
            set
            {
                PreCondition.AssertNotNull(schema, "schema");
                EnsureNotDisposed();

                SetDataWithUndoAction(schema, value);
            }
        }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new ScrumItemBase instance.
        /// </summary>
        protected ScrumItemBase()
        {
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Returns true if the specified state change could be accomplished.
        /// </summary>
        /// <param name="newState">Specifies the new state to validate (to).</param>
        public virtual bool CanChangeState(ScrumState newState)
        {
            return (StateIsReady || newState == State || newState == _lastOkState);
        }

        /// <summary>
        /// Returns true if the specified state is available
        /// </summary>
        /// <param name="newState">Specifies the new state to validate (to).</param>
        public bool ContainsChangeState(ScrumState newState)
        {
            return ItemType.StateMappings.Contains(newState);
        }

        /// <summary>
        /// Evaluates the corresponding meta data object from the work item type.
        /// </summary>
        /// <param name="mappedName">Specifies the mapped name (from schema) which is used to retrieve the expected element.</param>
        /// <returns>Returns the expected meta field or a null pointer if it couldn't be found.</returns>
        protected override DomainMeta TryGetFieldMeta(string mappedName)
        {
            return (from metaData in ItemType.FieldMeta
                    where mappedName == metaData.MappedName
                    select metaData).FirstOrDefault();
        }

        /// <summary>
        /// Initializes the mappings stored in the current instance.
        /// </summary>
        protected override void InitializeMappings()
        {
            base.InitializeMappings();

            foreach (SchemaBase childSchema in DomainSchema.Schemata)
            {
                if (childSchema.IsCustomSchema)
                {
                    PropertySchemaMappings.Add(childSchema, childSchema.SchemaName);
                }
            }

            PropertySchemaMappings.Add(NameSchema, "Name", true);
            PropertySchemaMappings.Add(AssignedMemberSchema, "AssignedMember", true);
            PropertySchemaMappings.Add(StateSchema, "State", (originalVal, newVal) => ItemType.HasAllBidirections() && !ItemType.HasStrictWorkflow);
            PropertySchemaMappings.Add(DescriptionSchema, "Description", true);
            PropertySchemaMappings.Add(RankingSchema, "Ranking", true);
        }

        /// <summary>
        /// Resets all child collections (IDomainCollection instances) of the
        /// current domain object.
        /// </summary>
        protected override void ResetChildCollections()
        {
            _attachments = null;
        }

        private void EnsureUserSchemaAvailable(string name)
        {
            PreCondition.AssertNotNullOrEmpty(name, "name");

            if (!DomainSchema.ContainsSchema(name))
            {
                throw new ArgumentException(string.Format(Resources.ExcScrItmBaseInvalidSchema, name));
            }
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }
}